import random
import time
import math
import copy
import warnings
warnings.filterwarnings("ignore")

PI = 3.1415926
NUMIND = 25
FUN = 1
crossPro = 0.9
mutaPro = 0.1

up=10
low=-10
x_dim = 2
#目标函数
def fun(x):
    return 3*(x[0]**2-x[1])**2

#进制选择

#
# x_dim=1
# def fun(x):
#     return x[0] * math.sin(10 * PI * x[0]) + 2.0
# low = -1
# up = 2




def check_border(x):
    if x<low:
        return low
    if x > 0:
        return 0
    return x


# 1.实数编码 2.交叉近亲回避 3.两点变异 4.多目标优化 5.目标函数用户输入
class Indivi():
    def __init__(self):
        self.fitness = 0.0
        self.x = [0.0]*10 #参数空间最大为100
        self.x2d = ['0' for i in range(25)]
        self.upLim = 0.0
        self.label = 0 #所属类别

class Popu:
    def __init__(self):
        self.indv = [Indivi() for i in range(NUMIND)]
        self.bestInd1 = Indivi()
        self.bestInd2 = Indivi()
        self.generan = 0

pop = Popu()


def ranF(x, y):
    return (((float(random.randint(0, 32767)) + 90.0) * (y - x) / 33956.0) + x)


# 目标函数 y=x·sin(10·PI·x)
def fit(x, i):
    if i == 1:
        return fun(x)
    else:
        return x

# 初始化种群
def initPop():
    pop.generan = 0
    for i in range(NUMIND):
        #随机分类
        pop.indv[i].label = i%2
        for j in range(x_dim):
            pop.indv[i].x[j] = random.uniform(low, up)

# 转换到[-1,2]
def calFit():
    for i in range(NUMIND):
        pop.indv[i].fitness = fit(pop.indv[i].x,FUN)

# upLim轮盘赌上界
# temp为下一个种群个体的适应值除以总适应值，同时上界为:temp+pop.indv[i].upLim
# 如果种群个体还没到最后一个，但上界已经超过了1.0，就输出错误信息，并推出exit(0)
# 如果没有错误，则最后一个的上界赋值为 1
def calUp():
    Sum = sum([pop.indv[i].fitness for i in range(NUMIND)])
    pop.indv[0].upLim = pop.indv[0].fitness / Sum
    for i in range(NUMIND - 1):
        temp = pop.indv[i + 1].fitness / Sum
        pop.indv[i + 1].upLim = temp + pop.indv[i].upLim
        if pop.indv[i + 1].upLim > 1.0 and (i + 1) < (NUMIND - 1):
            for j in range(NUMIND):
                print(
                    f"generation is: {pop.generan}, fitness = {pop.indv[j].fitness}, UpLim = {pop.indv[j].upLim}\n"
                )
            print(f"\nError,{i+1}'s upLim is greater than 1=============\n")
            exit(0)
    pop.indv[NUMIND - 1].upLim = 1

# 一个已排序的列表 InA 中查找一个值 value 的位置，并返回该位置的索引，LowBo：下限, UpBo：上限
def HalfSear(value, LowBo, UpBo, InA):
    Mid = (LowBo + UpBo) // 2
    if Mid == 0:
        return 0
    if (value <= InA[Mid]) and (value > InA[Mid - 1]):
        return Mid
    else:
        if value >= InA[Mid]:
            return HalfSear(value, Mid, UpBo, InA)
        else:
            return HalfSear(value, LowBo, Mid, InA)

# TemFitn列表存储每个个体在轮盘赌中被选中的概率
# 将pop.indv[NumTemp]设为新个体
def calSub():
    TemFitn = [pop.indv[i].upLim for i in range(NUMIND)]
    for i in range(NUMIND):
        rnd = ranF(0, 1)
        NumTemp = HalfSear(rnd, 0, NUMIND, TemFitn)
        pop.indv[i] = pop.indv[NumTemp]


def select():
    calUp()
    calSub()

# 基因交叉
# random.randint(a, b) 函数可以生成一个在区间 [a, b] 内的随机整数
# range(i, LENIND) 会生成一个范围对象
def swithstr(x1,x2,flag):
    w1 = random.uniform(0.6, 0.8)
    w2 = 1 - w1
    for i in range(x_dim):
        temp = w1*x1[i]+w2*x2[i]
        x2[i] = w1 * x2[i] + w2 * x1[i]
        x1[i] = temp
        #范围检查
        if flag==0:
            x1[i] = check_border(x1[i])
            x2[i] = check_border(x2[i])
        else:
            x1[i] = check_border(x1[i])
            x2[i] = check_border(x2[i])
    return x1,x2

#近亲回避
def crossover():
    for i in range(NUMIND // 2):
        if ranF(0, 1) > crossPro:
            pass
        else:
            labels = [pop.indv[i].label for i in range(NUMIND)]
            print(labels)
            index1 = [i for i in range(len(labels)) if labels[i] == 0]  # 个体下标
            index2 = [i for i in range(len(labels)) if labels[i] == 1]
            if int(ranF(0, 2)) == 0:
                if(len(index1) < 2):return
                # 直到jk不相等且不在同一个类，跳出循环
                j = int(ranF(0, len(index1)))
                k = int(ranF(0, len(index1)))
                while j==k:
                    j = int(ranF(0, len(index1)))
                    k = int(ranF(0, len(index1)))
                pop.indv[index1[j]].x, pop.indv[index1[k]].x = swithstr(pop.indv[index1[j]].x, pop.indv[index1[k]].x,0)
            else:
                # 直到jk不相等且不在同一个类，跳出循环
                if (len(index2) < 2): return
                j = int(ranF(0, len(index2)))
                k = int(ranF(0, len(index2)))
                while j==k:
                    j = int(ranF(0, len(index2)))
                    k = int(ranF(0, len(index2)))
                pop.indv[index2[j]].x, pop.indv[index2[k]].x = swithstr(pop.indv[index2[j]].x, pop.indv[index2[k]].x,1)


#两点变异
def changeM(x,flag):
    # 将传入的二进制串 ind 中某个位置上的 0 和 1 进行互换
    r1 = random.uniform(-0.2, 0.2)  # 扰动强度
    r2 = random.uniform(-0.2, 0.2)  # 扰动强度
    for i in range(x_dim):
        if flag==0:
            x[i] = check_border(x[i]+r1+r2)
        else:
            x[i] = check_border(x[i] + r1 + r2)
    return x


def mutation():
    for i in range(NUMIND):
        if ranF(0, 1) > mutaPro:
            continue
        else:
            pop.indv[i].x = changeM(pop.indv[i].x,pop.indv[i].label)


def max():
    max_fitness1 = -float("inf")
    max_index1 = 0
    max_fitness2 = -float("inf")
    max_index2 = 0
    for i in range(NUMIND):
        if pop.indv[i].label == 0:
            if pop.indv[i].fitness > max_fitness1:
                max_fitness1 = pop.indv[i].fitness
                max_index1 = i
        if pop.indv[i].label == 1:
            if pop.indv[i].fitness > max_fitness2:
                max_fitness2 = pop.indv[i].fitness
                max_index2 = i
    pop.bestInd1 = copy.deepcopy(pop.indv[max_index1])
    pop.bestInd2 = copy.deepcopy(pop.indv[max_index2])

import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
#聚类
def classification():
    global pop
    N = 2
    x1 = [pop.indv[i].x[0] for i in range(NUMIND)]
    x2 = [pop.indv[i].x[1] for i in range(NUMIND)]
    X = np.array(pd.DataFrame({'0': x1, '1': x2}))
    kmeans = KMeans(n_clusters=N)
    kmeans.fit(X)
    labels = kmeans.predict(X)
    for i in range(NUMIND):
        pop.indv[i].label = labels[i]

#递增序列
def isup(a):
    for i in range(len(a)-1):
        if a[i] > a[i+1]:
            return False
    return True
# 处理类不平衡问题
def unbalance():
    global pop
    labels = [pop.indv[i].label for i in range(NUMIND)]
    index1 = [i for i in range(len(labels)) if labels[i] == 0 ]#个体下标
    index2 = [i for i in range(len(labels)) if labels[i] == 1]
    fitnesses1 = [pop.indv[i].fitness for i in  index1]#个体适应值
    fitnesses2 = [pop.indv[i].fitness for i in index2]
    # print(index1,index2,fitnesses1,fitnesses2)#排序前
    #按适应度升序排序
    for i in range(len(index1)):
        for j in range(i+1,len(index1)):
            if pop.indv[index1[i]].fitness > pop.indv[index1[j]].fitness:
                temp = index1[i]
                index1[i] = index1[j]
                index1[j] = temp
    for i in range(len(index2)):
        for j in range(i+1,len(index2)):
            if pop.indv[index2[i]].fitness > pop.indv[index2[j]].fitness:
                temp = index2[i]
                index2[i] = index2[j]
                index2[j] = temp
    fitnesses1 = [pop.indv[i].fitness for i in index1]
    fitnesses2 = [pop.indv[i].fitness for i in index2]
    # print(index1,index2,fitnesses1,fitnesses2,isup(fitnesses1),isup(fitnesses2))#排序后
    num1 = len(index1)
    num2 = len(index2)
    if num1 < 5:
        new = 5-num1 #变动个数
        for i in range(new):
            j = pop.indv[index2[i]]#淘汰多数类适应度值最小的几个个体
            j.x[0] = random.uniform(low, up)#随机生成新个体
            j.x[1] = random.uniform(low, up)
            j.fitness = fit(j.x, FUN)#计算适应度
            j.label = 0#把标签设为少数类
    if num2 < 5:
        new = 5 - num2  # 变动个数
        for i in range(new):
            j = pop.indv[index1[i]]
            j.x[0] = random.uniform(low, up)
            j.x[1] = random.uniform(low, up)
            j.fitness = fit(j.x, FUN)
            j.label = 1

if __name__ == "__main__":
    # for i in range(100):
    #     print(int(ranF(1,5)),end=' ')
    random.seed(int(time.time()) % 9)
    initPop()
    with open("a.txt", "w") as fp:
        # 3.85027
        while pop.generan < 10000 and (pop.bestInd1.fitness < 36300 or pop.bestInd2.fitness < 36300):
            if pop.generan%10 == 1:
                classification()
                unbalance()
            for i in range(NUMIND):
                print(pop.indv[i].label,end='')
            print()
            calFit()
            max()
            print(f"The generation is {pop.generan}, the best1 is ({pop.bestInd1.x[:2]},{pop.bestInd1.fitness}) and best2 is ({pop.bestInd2.x[:2]},{pop.bestInd2.fitness})")
            select()
            crossover()
            if pop.generan == 100:
                mutaPro = 0.3
                print("The mutation probability is set to 0.3")
            mutation()
            pop.indv[0] = pop.bestInd1
            pop.indv[1] = pop.bestInd2
            pop.generan += 1
    print("finished")



